﻿/*
 * @Author: ws.s 
 * @Date: 2021-05-27 15:02:00 
 * @Last Modified by: ws.s
 * @Last Modified time: 2021-12-17 21:33:10
 */
using System.Reflection;
using System.Linq;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Text;

namespace WssUtils.EasyCSV
{
    /// <summary>
    /// csv帮助类
    /// v1.0.1 追加新的可解析的类型，float[] / int[]/ string[] 类型
    /// v1.0.2 修正 float[] int[] 的解析错误
    /// v1.0.3 增加新的转换接口，可以转换对象
    /// v1.0.4 更新增加 Boolean 值的转换 和 纯int 枚举的 转换，其他类型的枚举暂时无法转换
    /// v1.0.5 支持单独创建一个 CsvTable 对象，可以使用 CsvHelper 进行操作
    /// v1.0.6 支持 long long[] 类型，一般用于时间戳
    /// v1.0.7 支持 Double 类型，可以支持更大数值计算
    /// </summary>
    public static class CsvHelper
    {
        static Dictionary<string, CsvTable> tableDict;

        public static Action<FieldInfo, object, string> onConvertFieldValue;

        static StringBuilder _stringBuilder;

        static CsvHelper()
        {
            tableDict = new Dictionary<string, CsvTable>();
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="separator"></param>
        public static void Init(char separator = CsvTable.DEFAULT_SEPARATOR)
        {
            CsvTable.Init(separator);
            _stringBuilder = new StringBuilder();
        }

        /// <summary>
        /// 创建一个csv表格
        /// </summary>
        /// <param name="csvName">表格名称</param>
        /// <param name="data">csv结构的文本数据</param>
        /// <param name="resolveColumnName">是否解析列名</param>
        /// <param name="firstColumnIsID">第一列是否为id列</param>
        /// <returns></returns>
        public static CsvTable Create(string csvName, string data = "", bool resolveColumnName = true, bool firstColumnIsID = true)
        {
            var table = new CsvTable(csvName, data, resolveColumnName, firstColumnIsID);
            tableDict.Remove(csvName);
            tableDict.Add(csvName, table);

            return table;
        }

        /// <summary>
        ///  是否包含此表
        /// </summary>
        /// <param name="csvName"></param>
        /// <returns></returns>
        public static bool Contains(string csvName)
        {
            return tableDict.ContainsKey(csvName);
        }

        /// <summary>
        /// 获取表
        /// </summary>
        /// <param name="csvName"></param>
        /// <returns></returns>
        public static CsvTable Get(string csvName)
        {
            if (tableDict.ContainsKey(csvName))
            {
                return tableDict[csvName];
            }
            return null;
        }

        /// <summary>
        /// 使用id对应的行填充行数据
        /// 
        /// 【注】
        /// 1.csv表应第一列为id,且有列名
        /// 2.rowData中的字段名称应一一映射csv表中的列名
        /// 3.rowData中的字段类型实现 IConvertible
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="csvName"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public static T PaddingData<T>(string csvName, string id) where T : new()
        {
            var rowData = new T();
            object temp = rowData;

            var table = Get(csvName);
            if (table == null) return rowData;

            var row = table.GetRowByID(id);

            return PaddingData<T>(csvName, row);
        }

        public static T PaddingData<T>(CsvTable table, string id) where T : new()
        {
            var rowData = new T();
            object temp = rowData;
            if (table == null) return rowData;
            var row = table.GetRowByID(id);
            return PaddingData<T>(table, row);
        }


        /// <summary>
        /// 反射 处理字段数据，重新写入对象
        /// </summary>
        /// <param name="field"></param>
        /// <param name="temp"></param>
        /// <param name="valueStr"></param>
        public static void ConvertFieldValue(FieldInfo field, object temp, string valueStr)
        {
            switch (field.FieldType.Name)
            {
                case "Int32[]":
                    var arr = valueStr.Split(',');
                    var intArr = new int[arr.Count()];
                    int intValue = 0;
                    for (int i = 0; i < intArr.Length; i++)
                    {
                        int.TryParse(arr[i], out intValue);
                        intArr[i] = intValue;
                    }
                    field.SetValue(temp, intArr);
                    break;
                case "Int64[]":
                    arr = valueStr.Split(',');
                    var longArr = new long[arr.Count()];
                    long longValue = 0;
                    for (int i = 0; i < longArr.Length; i++)
                    {
                        long.TryParse(arr[i], out longValue);
                        longArr[i] = longValue;
                    }
                    field.SetValue(temp, longArr);
                    break;
                case "Single[]":
                    arr = valueStr.Split(',');
                    var floatArr = new float[arr.Length];
                    float floatValue = 0;
                    for (int i = 0; i < floatArr.Length; i++)
                    {
                        float.TryParse(arr[i], out floatValue);
                        floatArr[i] = floatValue;
                    }
                    field.SetValue(temp, floatArr);
                    break;
                case "Double[]":
                    arr = valueStr.Split(',');
                    var doubleArr = new double[arr.Length];
                    double doubleValue = 0;
                    for (int i = 0; i < doubleArr.Length; i++)
                    {
                        double.TryParse(arr[i], out doubleValue);
                        doubleArr[i] = doubleValue;
                    }
                    field.SetValue(temp, doubleArr);
                    break;
                case "String[]":
                    field.SetValue(temp, valueStr.Split(','));
                    break;
                case "Int32":
                case "Int64":
                case "Single":
                case "String":
                case "Double":
                    var value = Convert.ChangeType(valueStr, field.FieldType);
                    field.SetValue(temp, value);
                    break;
                case "Boolean":
                    bool b = valueStr.Equals("1") || valueStr.Equals("true");
                    field.SetValue(temp, b);
                    break;
                default:
                    if (field.FieldType.IsEnum)
                    {
                        intValue = 0;
                        //自动处理特殊的枚举类型
                        foreach (var item in Enum.GetValues(field.FieldType))
                        {
                            if (valueStr.ToLower() == item.ToString().ToLower())
                            {
                                field.SetValue(temp, item);
                            }
                            else
                            {
                                int a = Convert.ToInt32(item);
                                int.TryParse(valueStr, out intValue);
                                if (intValue == a)
                                {
                                    field.SetValue(temp, item);
                                }

                            }
                        }

                    }
                    else
                    {
                        //自定义类型转换
                        onConvertFieldValue?.Invoke(field, temp, valueStr);
                    }

                    break;
            }

        }

        /// <summary>
        /// 使用id对应的行填充行数据
        /// 
        /// 【注】
        /// 1.csv表应第一列为id,且有列名
        /// 2.rowData中的字段名称应一一映射csv表中的列名
        /// 3.rowData中的字段类型实现 IConvertible (可序列化)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="csvName"></param>
        /// <param name="row"></param>
        /// <returns></returns>
        public static T PaddingData<T>(string csvName, int row) where T : new()
        {
            var table = Get(csvName);
            if (table == null) return new T();
            return PaddingData<T>(table, row);
        }

        /// <summary>
        /// 使用id对应的行填充行数据
        /// 
        /// 【注】
        /// 1.csv表应第一列为id,且有列名
        /// 2.rowData中的字段名称应一一映射csv表中的列名
        /// 3.rowData中的字段类型实现 IConvertible (可序列化)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="csvName"></param>
        /// <param name="row"></param>
        /// <returns></returns>
        public static T PaddingData<T>(CsvTable table, int row) where T : new()
        {
            var rowData = new T();
            object temp = rowData;

            if (table == null) return rowData;

            if (row < 0 || row > table.RowCount) return rowData;

            var fields = typeof(T).GetFields();
            foreach (var field in fields)
            {
                var valueStr = table.Read(row, field.Name);
                if (string.IsNullOrEmpty(valueStr)) continue;

                try
                {
                    ConvertFieldValue(field, temp, valueStr);
                }
                catch (Exception e)
                {
                    Debug.LogErrorFormat("Csv padding data error! {0} At " + table.Name + ", row:" + row, e);
                }
            }
            rowData = (T)temp;

            return rowData;
        }

        public static IDictionary<string, T> ConvertToDictionary<T>(string csvName, int startRow = 2) where T : new()
        {
            var table = Get(csvName);
            if (table == null) return new Dictionary<string, T>();
            return ConvertToDictionary<T>(table, startRow);
        }

        public static IDictionary<string, T> ConvertToDictionary<T>(CsvTable table, int startRow = 2) where T : new()
        {
            var dic = new Dictionary<string, T>();
            if (table == null) return dic;
            for (int i = startRow; i < table.RowCount; i++)
            {
                var key = table.Read(i, 0);
                var value = PaddingData<T>(table, i);
                if (string.IsNullOrEmpty(key))
                {
                    continue;
                }
                dic.Add(key, value);
            }
            return dic;
        }



        public static IList<T> ConvertToList<T>(string csvName, int startRow = 2) where T : new()
        {
            var table = Get(csvName);
            if (table == null) return new List<T>();
            return ConvertToList<T>(table, startRow);
        }

        public static IList<T> ConvertToList<T>(CsvTable table, int startRow = 2) where T : new()
        {
            var list = new List<T>();
            if (table == null) return list;
            for (int i = startRow; i < table.RowCount; i++)
            {
                string checkFirstCol = table.Read(i, 0);
                if (string.IsNullOrEmpty(checkFirstCol))
                {
                    continue;
                }
                var value = PaddingData<T>(table, i);
                list.Add(value);
            }
            return list;
        }

        public static T ConvertToObject<T>(string csvName, ref object temp, int startRow = 2) where T : new()
        {
            CsvTable table = CsvHelper.Get(csvName);
            return ConvertToObject<T>(table, ref temp, startRow);
        }

        public static T ConvertToObject<T>(CsvTable table, ref object temp, int startRow = 2) where T : new()
        {
            var fields = typeof(T).GetFields();
            if (temp == null) temp = new T();
            foreach (var field in fields)
            {
                var valueStr = table.Read(field.Name, "value");
                if (string.IsNullOrEmpty(valueStr)) continue;
                try
                {
                    CsvHelper.ConvertFieldValue(field, temp, valueStr);
                }
                catch (System.Exception e)
                {
                    Debug.LogError("Filed Error:" + field.Name + " param:" + valueStr);
                    Debug.LogErrorFormat("Csv padding data error! {0}", e);
                }
            }
            return (T)temp;
        }

        /// <summary>
        /// 将字段转换为 CSV 原始数据 (方便保存信息)
        /// </summary>
        /// <param name="listData">列表数据</param>
        /// <param name="filterString">过滤字符串，过滤需要的字符串</param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static string ConvertToCsvString(IEnumerable<object> listData, string split = ",", string[] filterString = null)
        {
            if (listData.Count() < 1) return string.Empty;
            var lineBreak = CsvTable.GetLinkBreak(CsvTable.LineBreak.CRLF);
            _stringBuilder.Length = 0;

            //反射获取T的属性值
            // 属性列
            // 类型列
            _stringBuilder.Append(ConvertToCsvStringLineFields(listData.First(), split, filterString));

            // 数值列
            foreach (var data in listData)
            {
                _stringBuilder.Append(lineBreak);
                _stringBuilder.Append(ConvertToCsvStringLineValue(data, split, filterString));
            }

            var res = _stringBuilder.ToString();
            _stringBuilder.Length = 0;
            return res;
        }


        private static string ConvertToCsvStringLineValue(object obj, string split = ",", string[] filterString = null)
        {
            var sp = split;
            List<string> valueList = new List<string>();
            foreach (System.Reflection.FieldInfo p in obj.GetType().GetFields())
            {
                if (filterString != null && filterString.Contains(p.Name)) continue;
                if (p.GetValue(obj) == null) continue;

                switch (p.GetValue(obj).GetType().Name)
                {
                    case "Int64":
                    case "Int32":
                    case "Single":
                    case "String":
                        var str = p.GetValue(obj).ToString();
                        //如果含有分隔符号要注意
                        if (str.Contains(split))
                        {
                            str = "\"" + str + "\"";
                        }
                        valueList.Add(str);
                        break;
                    case "Int32[]":
                    case "Int64[]":
                    case "Single[]":
                    case "String[]":
                        str = string.Join(",", (Array)p.GetValue(obj));
                        if (str.Contains(split))
                        {
                            str = "\"" + str + "\"";
                        }
                        valueList.Add(str);
                        break;
                    default:
                        // Debug.Log("不支持转换的类型: " + p.Name + "," + p.GetValue(obj).GetType().Name);
                        break;
                }
            }

            return string.Join(sp, valueList);
        }

        private static string ConvertToCsvStringLineFields(object obj, string split = ",", string[] filterString = null)
        {
            List<string> lineListProperty = new List<string>();
            List<string> lineListType = new List<string>();
            var sp = split;
            var lineBreak = CsvTable.GetLinkBreak(CsvTable.LineBreak.CRLF);

            foreach (System.Reflection.FieldInfo p in obj.GetType().GetFields())
            {
                if (filterString != null && filterString.Contains(p.Name)) continue;
                if (p.GetValue(obj) == null) continue;

                switch (p.GetValue(obj).GetType().Name)
                {
                    case "Int32":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("int");
                        break;
                    case "Int64":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("long");
                        break;
                    case "Single":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("float");
                        break;
                    case "String":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("string");
                        break;
                    case "Int32[]":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("int[]");
                        break;
                    case "Int64[]":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("long[]");
                        break;
                    case "Single[]":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("float[]");
                        break;
                    case "String[]":
                        lineListProperty.Add(p.Name);
                        lineListType.Add("string[]");
                        break;
                    default:
                        lineListProperty.Add(p.Name);
                        lineListType.Add(p.GetValue(obj).GetType().Name);
                        // Debug.LogWarning("不支持转换的类型: " + p.Name + "," + p.GetValue(obj).GetType().Name);
                        break;
                }
            }

            return string.Join(sp, lineListProperty) + lineBreak + string.Join(sp, lineListType);
        }

        /// <summary>
        /// 提前检查一个 CSV 数据是否有效
        /// </summary>
        /// <param name="csvData"></param>
        /// <returns></returns>
        public static bool IsCsvStringValid(Type type, string csvData, out string ErrorReason)
        {
            var fields = type.GetFields();
            var dataList = csvData.Split(CsvTable.GetLinkBreak(CsvTable.LineBreak.CRLF).ToCharArray()[0]);
            if (dataList.Length <= 2)
            {
                ErrorReason = "数据数量不够";
                return false;
            }

            foreach (FieldInfo field in fields)
            {
                //只是检查6种类型,其他不检查
                switch (field.FieldType.Name)
                {
                    case "Int32":
                    case "Int64":
                    case "Single":
                    case "String":
                    case "Int32[]":
                    case "Int64[]":
                    case "Single[]":
                    case "String[]":
                        bool res = dataList[0].Contains(field.Name);
                        if (res == false)
                        {
                            ErrorReason = "缺少字段" + field.Name;
                            return false;
                        }
                        break;
                    default:
                        break;
                }
            }

            ErrorReason = "";
            return true;
        }

        /// <summary>
        /// 释放
        /// </summary>
        public static void Clear()
        {
            tableDict.Clear();
        }

    }
}
